using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using Sedge.Core.Configuration.Property;
using Sedge.Core.Configuration.Report;
using Sedge.Core.Configuration.Transport;
using Sedge.Core.Globalization;
using Sedge.Core.Utils;

namespace Sedge.Core.Configuration
{
	public static class XmlToSuite
	{
		#region Configuration names

		private const string SuiteTag = "Suite";
		private const string SuitePartTag = "SuitePart";

		private const string ApplicationTag = "Application";
		private const string PropertiesTag = "Properties";
		private const string PropertyTag = "Property";
		private const string RegPropertyTag = "RegistryProperty";
		private const string CustomPropertyTag = "CustomProperty";
		private const string OptionTag = "Option";
		private const string SetupTag = "Setup";
		private const string ScheduleTag = "Schedule";
		private const string StepTag = "Step";
		private const string TransportTag = "Transport";

		private const string ReportTag = "Report";
		private const string GroupTag = "Group";
		private const string PageTag = "Page";

		private const string FileSourceTag = "File";
		private const string InfoSourceTag = "Info";
		private const string TempSourceTag = "Temp";
		private const string DataSourceTag = "Data";
		private const string TableSourceTag = "Table";

		private const string NameAttr = "name";
		private const string CaptionAttr = "caption";
		private const string PartialAttr = "partial";
		private const string ValueAttr = "value";
		private const string KeyAttr = "key";
		private const string AssemblyAttr = "assembly";
		private const string PathAttr = "path";
		private const string PathRecursive = "recursive";

		private const string IfValue = "if-value";
		private const string IfTrue = "if-true";
		private const string IfFalse = "if-false";

		private const string IfFileExists = "fileexists";
		private const string IfFolderExists = "folderexists";
		private const string IfEmpty = "empty";

		#endregion // Configuration names

		#region Private Fields and Methods

		private delegate ISource SourceProcessFunc(ReportableObject owner, XmlNode node);
		private static readonly Dictionary<string, SourceProcessFunc> _sourceHandlers;

		static XmlToSuite()
		{
			_sourceHandlers = new Dictionary<string, SourceProcessFunc>
			                  	{
											{FileSourceTag, ProcessFileSourceNode},
											{InfoSourceTag, ProcessInfoSourceNode},
											{TempSourceTag, ProcessTempSourceNode},
											{DataSourceTag, ProcessDataSourceNode},
											{TableSourceTag, ProcessTableSourceNode}
			                  	};
		}

		#endregion //Private Fields and Methods

		#region Base Functions

		public static XmlNode GetSuiteNode(XmlDocument xmlDocument, bool part)
		{
			XmlNode result = null;

			XmlNodeList xmlNodes = xmlDocument.GetElementsByTagName(part ? SuitePartTag : SuiteTag);
			if (xmlNodes.Count != 0)
			{
				result = xmlNodes[0];
			}

			return result;
		}

		public static string GetSuiteNodeName(XmlNode xmlNode)
		{
			string result = String.Empty;
			if (xmlNode != null && xmlNode.Attributes[NameAttr] != null)
				result = xmlNode.Attributes[NameAttr].Value;
			return result;
		}

		public static T CreateReportableObjectFromXml<T>(XmlNode xmlNode, ReportableObject parent, IDictionary<string, string> predefProp) where T : ReportableObject, new()
		{
			T resultObject = new T
			{
				Parent = parent
			};

			if (xmlNode.Attributes[NameAttr] != null)
				resultObject.Name = xmlNode.Attributes[NameAttr].Value;
			if (xmlNode.Attributes[CaptionAttr] != null)
				resultObject.Caption = xmlNode.Attributes[CaptionAttr].Value;

			if (predefProp != null)
			{
				foreach (var prop in predefProp)
				{
					resultObject.Properties.Add(prop.Key, prop.Value);
				}
			}

			ProcessPropertiesNode(resultObject, xmlNode);

			return resultObject;
		}

		public static Options GetOptionsForNode(ReportableObject owner, XmlNode xmlNode)
		{
			IDictionary<string, string> optionsDictionary = new Dictionary<string, string>();
			XmlNodeList options = xmlNode.SelectNodes(OptionTag);
			if (options != null)
			{
				foreach (XmlNode optionNode in options)
				{
					if (optionNode.Attributes[NameAttr] != null && optionNode.Attributes[ValueAttr] != null)
					{
						if (!VerifyIfCondition(owner, optionNode))
							continue;

						optionsDictionary[optionNode.Attributes[NameAttr].Value] =
							PropertyResolver.ResolveValue(owner, optionNode.Attributes[ValueAttr].Value);
					}
				}
			}

			return new Options(optionsDictionary);
		}

		#endregion //Base Functions

		#region Process Functions

		public static bool VerifyIfCondition(ReportableObject owner, XmlNode xmlNode)
		{
			if (xmlNode.Attributes[IfValue] == null || (xmlNode.Attributes[IfValue] != null && xmlNode.Attributes[IfTrue] == null && xmlNode.Attributes[IfFalse] == null))
				return true;

			string value = PropertyResolver.ResolveValue(owner, xmlNode.Attributes[IfValue].Value);
			string condition = xmlNode.Attributes[IfTrue] != null
										? xmlNode.Attributes[IfTrue].Value.ToLower() : xmlNode.Attributes[IfFalse].Value.ToLower();

			if (String.IsNullOrEmpty(condition))
				return true;

			bool result = true;
			if (condition == IfFileExists)
			{
				result = Services.FileSystem.File.Exists(value);
			}
			else if (condition == IfFolderExists)
			{
				result = Services.FileSystem.Directory.Exists(value);
			}
			else if (condition == IfEmpty)
			{
				result = String.IsNullOrEmpty(value);
			}

			return xmlNode.Attributes[IfTrue] != null ? result : !result;
		}

		public static bool ProcessSetupNode(Suite suite, XmlNode xmlNode)
		{
			XmlNodeList setupOptions = xmlNode.SelectNodes(SetupTag);
			if (setupOptions != null && setupOptions.Count != 0)
			{
				suite.Options = GetOptionsForNode(suite, setupOptions[0]);
			}

			bool isPartial = false;
			if (xmlNode.Attributes[PartialAttr] != null)
				isPartial = xmlNode.Attributes[PartialAttr].Value == "yes";

			return isPartial;
		}

		public static void ProcessTransportNode(Suite suite, XmlNode xmlNode)
		{
			XmlNodeList transports = xmlNode.SelectNodes(TransportTag);
			if (transports == null || transports.Count == 0)
				return;

			string assembly = String.Empty;
			string name = String.Empty;
			XmlNode transportNode = transports[0];
			if (transportNode.Attributes[AssemblyAttr] != null)
				assembly = transportNode.Attributes[AssemblyAttr].Value;
			if (transportNode.Attributes[NameAttr] != null)
				name = transportNode.Attributes[NameAttr].Value;

			if (String.IsNullOrEmpty(name))
				return;
			if (String.IsNullOrEmpty(assembly))
				assembly = Assembly.GetExecutingAssembly().Location;
			ITransport transport = TypeActivator.Activate<ITransport>(assembly, name + "Transport");
			if (transport != null)
			{
				transport.Options = GetOptionsForNode(suite, transportNode);
				suite.Transport = transport;
			}
		}

		public static void ProcessPropertiesNode(ReportableObject owner, XmlNode xmlNode)
		{
			XmlNodeList properties = xmlNode.SelectNodes(String.Format("{0}/*", PropertiesTag));
			if (properties != null)
			{
				foreach (XmlNode node in properties)
				{
					if (!VerifyIfCondition(owner, node))
						continue;

					string nodeName = node.Name;
					string propertyName;
					if (node.Attributes[NameAttr] != null)
						propertyName = node.Attributes[NameAttr].Value;
					else
					{
						ErrorHandler.Warning(Locale.Strings.WMissedAttribute, NameAttr);
						continue;
					}

					IPropertyHandler propertyHandler = null;
					if (nodeName.Equals(PropertyTag))
					{
						if (node.Attributes[ValueAttr] != null)
							propertyHandler = new PropertyHandler(owner, node.Attributes[ValueAttr].Value);
					}
					else if (nodeName.Equals(RegPropertyTag))
					{
						if (node.Attributes[KeyAttr] != null)
							propertyHandler = new RegPropertyHandler(owner, node.Attributes[KeyAttr].Value, node.Attributes[ValueAttr].Value);
					}
					else if (nodeName.Equals(CustomPropertyTag))
					{
						if (node.Attributes[AssemblyAttr] != null && node.Attributes[ValueAttr] != null)
						{
							Options options = GetOptionsForNode(owner, node);
							propertyHandler = new CustomPropertyHandler(owner, node.Attributes[AssemblyAttr].Value,
															  String.Concat(node.Attributes[ValueAttr].Value, "Property"),
															  options);
						}
					}
					else
					{
						ErrorHandler.Warning(Locale.Strings.WInvalidPropertyTag, nodeName);
					}

					if (propertyHandler != null)
					{
						string value = propertyHandler.GetValue();
						if (!String.IsNullOrEmpty(value))
						{
							owner.Properties[propertyName] = value;
						}
					}
				}
			}
		}

		public static void ProcessApplicationsNode(Suite suite, XmlNode parentNode, string appName)
		{
			XmlNodeList applications = parentNode.SelectNodes(ApplicationTag);
			if (applications == null)
				return;

			foreach (XmlNode appNode in applications)
			{
				if (!VerifyIfCondition(suite, appNode))
					continue;

				ReportableObject appObject = CreateReportableObjectFromXml<ReportableObject>(appNode, suite, null);
				if (appObject != null)
				{
					for (int i = 0; i < suite.Applications.Count; i++)
					{
						if (suite.Applications[i].Name.Equals(appObject.Name))
						{
							suite.Applications[i] = appObject;
							ErrorHandler.Warning(Locale.Strings.WDuplicatedAppName, appObject.Name);
							return;
						}
					}

					ProcessReportNode(appObject, appNode);
					suite.Applications.Add(appObject);
					if (appObject.Name.Equals(appName))
					{
						suite.SelectedApplication = appObject;
						break;
					}
				}
			}
		}

		public static void ProcessReportNode(ReportableObject owner, XmlNode xmlNode)
		{
			XmlNodeList groups = xmlNode.SelectNodes(String.Format("{0}/{1}", ReportTag, GroupTag));
			if (groups != null)
			{
				foreach (XmlNode node in groups)
				{
					if (!VerifyIfCondition(owner, node))
						continue;

					string name = String.Empty;
					string caption = String.Empty;
					if (node.Attributes[NameAttr] != null)
					{
						name = PropertyResolver.ResolveValue(owner, node.Attributes[NameAttr].Value);
					}
					if (node.Attributes[CaptionAttr] != null)
					{
						caption = PropertyResolver.ResolveValue(owner, node.Attributes[CaptionAttr].Value);
					}

					if (!String.IsNullOrEmpty(name))
					{
						Group group = new Group { Name = name, Caption = caption };
						ProcessGroupNode(owner, node, group);
						owner.Groups.Add(group);
					}
					else
					{
						ErrorHandler.Warning(Locale.Strings.WMissedAttribute, "name");
					}
				}
			}
		}

		private static void ProcessGroupNode(ReportableObject owner, XmlNode xmlNode, Group group)
		{
			foreach (XmlNode node in xmlNode.ChildNodes)
			{
				if (!node.Name.Equals(PageTag))
				{
					ErrorHandler.Warning(Locale.Strings.WInvalidGroupTag, node.Name);
					continue;
				}

				if (!VerifyIfCondition(owner, node))
					continue;

				string name = String.Empty;
				string caption = String.Empty;
				if (node.Attributes[NameAttr] != null)
				{
					name = PropertyResolver.ResolveValue(owner, node.Attributes[NameAttr].Value);
				}
				if (node.Attributes[CaptionAttr] != null)
				{
					caption = PropertyResolver.ResolveValue(owner, node.Attributes[CaptionAttr].Value);
				}

				Page page = new Page { Name = name, Caption = caption };
				ProcessPageNode(owner, node, page);
				group.Pages.Add(page);
			}
		}

		private static ISource ProcessFileSourceNode(ReportableObject owner, XmlNode node)
		{
			string path = String.Empty;
			bool recursive = false;

			if (node.Attributes[PathAttr] != null)
			{
				path = PropertyResolver.ResolveValue(owner, node.Attributes[PathAttr].Value);
			}

			if (node.Attributes[PathRecursive] != null && node.Attributes[PathRecursive].Value.ToLower() == "yes")
			{
				recursive = true;
			}

			if (String.IsNullOrEmpty(path))
			{
				ErrorHandler.Warning(Locale.Strings.WMissedAttribute, PathAttr);
				return null;
			}

			ISource source = new FileSource(path, recursive);
			SetCaption(owner, source, node);
			return source;
		}

		private static ISource ProcessInfoSourceNode(ReportableObject owner, XmlNode node)
		{
			string path = String.Empty;
			bool recursive = false;

			if (node.Attributes[PathAttr] != null)
			{
				path = PropertyResolver.ResolveValue(owner, node.Attributes[PathAttr].Value);
			}

			if (node.Attributes[PathRecursive] != null && node.Attributes[PathRecursive].Value.ToLower() == "yes")
			{
				recursive = true;
			}

			if (String.IsNullOrEmpty(path))
			{
				ErrorHandler.Warning(Locale.Strings.WMissedAttribute, PathAttr);
				return null;
			}

			ISource source = new InfoSource(path, recursive);
			SetCaption(owner, source, node);
			return source;
		}

		private static ISource ProcessTableSourceNode(ReportableObject owner, XmlNode node)
		{
			Options options = GetOptionsForNode(owner, node);
			TableSource source = new TableSource { Options = options };
			SetCaption(owner, source, node);
			return source;
		}

		private static ISource ProcessDataSourceNode(ReportableObject owner, XmlNode node)
		{
			if (node.Attributes[NameAttr] == null)
			{
				ErrorHandler.Warning(Locale.Strings.WMissedAttribute, NameAttr);
				return null;
			}

			string name = node.Attributes[NameAttr].Value;
			string assemblyFile = String.Empty;
			if (node.Attributes[AssemblyAttr] != null)
			{
				assemblyFile = PropertyResolver.ResolveValue(owner, node.Attributes[AssemblyAttr].Value);
			}

			Options options = GetOptionsForNode(owner, node);
			IDataSource dataSource = TypeActivator.Activate<IDataSource>(assemblyFile, name + "Source");
			if (dataSource != null)
			{
				dataSource.Options = options;
				SetCaption(owner, dataSource, node);
			}

			return dataSource;
		}

		private static void SetCaption(ReportableObject owner, ISource source, XmlNode node)
		{
			if (node.Attributes[CaptionAttr] != null)
				source.Caption = PropertyResolver.ResolveValue(owner, node.Attributes[CaptionAttr].Value);
		}

		private static ISource ProcessTempSourceNode(ReportableObject owner, XmlNode node)
		{
			if (node.Attributes[NameAttr] == null)
			{
				ErrorHandler.Warning(Locale.Strings.WMissedAttribute, NameAttr);
				return null;
			}

			ISource tempData = new TempSource(node.Attributes[NameAttr].Value.ToLower());
			SetCaption(owner, tempData, node);
			return tempData;
		}

		private static void ProcessPageNode(ReportableObject owner, XmlNode xmlNode, Page page)
		{
			foreach (XmlNode node in xmlNode.ChildNodes)
			{
				if (!_sourceHandlers.ContainsKey(node.Name))
				{
					ErrorHandler.Warning(Locale.Strings.WInvalidSourceTag, node.Name);
					continue;
				}

				if (!VerifyIfCondition(owner, node))
					continue;

				SourceProcessFunc processFunc = _sourceHandlers[node.Name];
				if (processFunc != null)
				{
					ISource source = processFunc(owner, node);
					if (source != null)
						page.Sources.Add(source);
				}
			}
		}

		public static void ProcessScheduleNode(Suite suite, XmlNode suiteNode)
		{
			XmlNodeList steps = suiteNode.SelectNodes(String.Format("{0}/{1}", ScheduleTag, StepTag));
			if (steps != null && steps.Count != 0)
			{
				foreach (XmlNode stepNode in steps)
				{
					if (!VerifyIfCondition(suite, stepNode))
						continue;

					if (stepNode.Attributes[NameAttr] != null)
					{
						string name = stepNode.Attributes[NameAttr].Value;

						string assembly = String.Empty;
						if (stepNode.Attributes[AssemblyAttr] != null)
							assembly = PropertyResolver.ResolveValue(suite, stepNode.Attributes[AssemblyAttr].Value);

						Options options = GetOptionsForNode(suite, stepNode);
						suite.Steps.Add(new Step(name, assembly, options));
					}
					else
					{
						ErrorHandler.Warning(Locale.Strings.WMissedAttribute, NameAttr);
					}
				}
			}

			if (suite.Steps.Count == 0)
				ErrorHandler.Warning(Locale.Strings.WEmptySchedule);
		}
		#endregion //Process Functions
	}
}